Skip to content

feat(web): customizable login branding page#1367

Open
thomasbeaudry wants to merge 21 commits into
mainfrom
AddBranding
Open

feat(web): customizable login branding page#1367
thomasbeaudry wants to merge 21 commits into
mainfrom
AddBranding

Conversation

@thomasbeaudry

@thomasbeaudry thomasbeaudry commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Add an admin "Customize Login Page" route that lets administrators brand the login screen, rendered live by a new shared by the editor preview and the real login page.

Configurable branding:

  • Instance name, main description (tagline), details, and resource links (bilingual EN/FR), each independently orderable and toggleable.
  • Logo: choose between an uploaded image or an image URL via a radio, with both slots persisted; falls back to the default logo if a URL 404s.
  • Per-section font size (10-72px), bold, and name alignment.
  • Left- and right-panel gradient themes (presets or custom hex) and a single left-panel text color.

Also:

  • Surface the instance name atop the login form when the branding panel is hidden (below lg / high zoom).
  • Fix horizontal scroll + grey area below the footer (w-screen -> w-full on the layout, which included the scrollbar gutter).
  • Keep the footer copyright year live via a useCurrentYear hook (the old module-scope constant never rolled over without a reload).
  • Persist branding via a BrandingConfig composite type (Prisma + Zod), with backward-compatible migration of the legacy single logo field.

Summary by CodeRabbit

  • New Features

    • Full customizable login branding: bilingual texts, logo (upload or URL), resource links, typography, section ordering, theme palettes (including custom colors) and live preview.
    • Admin "Customize Login Page" screen with validation, preview, save, and unsaved-changes guard.
    • Login page now shows a branding panel on large screens and applies branding gradients.
    • Footer year now updates reliably across long sessions.
  • Bug Fixes

    • Improved layout overflow handling and navigation behavior.

Add an admin "Customize Login Page" route that lets administrators brand
the login screen, rendered live by a new <LoginBrandingPanel> shared by
the editor preview and the real login page.

Configurable branding:
- Instance name, main description (tagline), details, and resource links
  (bilingual EN/FR), each independently orderable and toggleable.
- Logo: choose between an uploaded image or an image URL via a radio, with
  both slots persisted; falls back to the default logo if a URL 404s.
- Per-section font size (10-72px), bold, and name alignment.
- Left- and right-panel gradient themes (presets or custom hex) and a
  single left-panel text color.

Also:
- Surface the instance name atop the login form when the branding panel is
  hidden (below lg / high zoom).
- Fix horizontal scroll + grey area below the footer (w-screen -> w-full on
  the layout, which included the scrollbar gutter).
- Keep the footer copyright year live via a useCurrentYear hook (the old
  module-scope constant never rolled over without a reload).
- Persist branding via a BrandingConfig composite type (Prisma + Zod), with
  backward-compatible migration of the legacy single logo field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thomasbeaudry thomasbeaudry requested a review from joshunrau as a code owner June 3, 2026 16:40
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4ad83e0b-4eca-4f36-b206-97e73f3c1be4

📥 Commits

Reviewing files that changed from the base of the PR and between dc95385 and d8c5fa2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/web/src/routes/_app/admin/branding.tsx
  • packages/schemas/src/setup/setup.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/schemas/src/setup/setup.ts

Walkthrough

Adds end-to-end login-page branding: Zod and Prisma types, API DTO/service persistence, gradient utilities, LoginBrandingPanel + stories, a full /admin/branding editor with live preview/validation, login-page integration, routing, and supporting UI hooks.

Changes

Login Page Branding Customization

Layer / File(s) Summary
Branding Schemas & Types
packages/schemas/src/setup/setup.ts
Defines enums/const arrays and Zod schemas ($BrandingText, $ResourceLink, $BrandingConfig), validators, updates $SetupState.branding and $UpdateSetupStateData.
Prisma Schema & API DTO
apps/api/prisma/schema.prisma, apps/api/src/setup/dto/update-setup-state.dto.ts
Adds Prisma embedded types (BrandingText, ResourceLink, BrandingConfig), extends SetupState with branding, and exposes optional branding on UpdateSetupStateDto.
Backend Service Implementation
apps/api/src/setup/setup.service.ts
Parses persisted branding via $BrandingConfig.safeParse in getState; updateState destructures branding and conditionally persists it using Prisma composite set semantics.
Branding Utilities & LoginBrandingPanel
apps/web/src/utils/branding.ts, apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx, apps/web/src/components/LoginBranding/index.ts
Adds LOGIN_THEME_COLORS, gradient helpers (resolveLoginThemeColors, getLoginGradient, getRightPanelGradient) and implements LoginBrandingPanel with logo handling, bilingual texts, section ordering, preview mode, and an index re-export.
LoginBrandingPanel Storybook Stories
apps/web/src/components/LoginBranding/LoginBrandingPanel.stories.tsx
Adds stories: Default, Preview, WithResources, CustomGradient demonstrating component variants.
Admin Branding Page
apps/web/src/routes/_app/admin/branding.tsx
Implements /admin/branding route: comprehensive form state, legacy migration, validation, file upload vs URL logo handling, color pickers, section reorder UI, unsaved-changes guard, normalization on save, live EN/FR preview, and fullscreen preview Dialog.
Login Route Integration & Routing
apps/web/src/routes/auth/login.tsx, apps/web/src/route-tree.ts, apps/web/src/hooks/useNavItems.ts
Registers /admin/branding in generated route tree, updates admin nav to “Customize Login Page”, and integrates the branding panel and right-panel gradients into the login page responsive layout.
Supporting Hooks & Polishing
apps/web/src/hooks/useUpdateSetupStateMutation.ts, apps/web/src/hooks/useCurrentYear.ts, apps/web/src/components/Footer/Footer.tsx, apps/web/src/components/Layout/Layout.tsx, apps/web/src/routes/_app/admin/settings.tsx
Adds useCurrentYear hook and integrates it into Footer; adjusts Layout overflow behavior; extends update hook to accept optional successNotification; narrows settings form validation to isExperimentalFeaturesEnabled.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • joshunrau
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(web): customizable login branding page' clearly and specifically summarizes the main change—adding a new customizable login branding feature to the web app.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch AddBranding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
packages/schemas/src/setup/setup.ts (2)

126-126: 💤 Low value

sectionsOrder allows duplicate sections.

The schema permits arrays like ['logo', 'logo', 'name']. If duplicates would cause rendering issues, add a refinement.

♻️ Optional: add uniqueness check
-  sectionsOrder: z.array(z.enum(PANEL_SECTIONS)).max(5).optional(),
+  sectionsOrder: z.array(z.enum(PANEL_SECTIONS)).max(5).refine(
+    (arr) => new Set(arr).size === arr.length,
+    'Section order must not contain duplicates'
+  ).optional(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/schemas/src/setup/setup.ts` at line 126, The sectionsOrder schema
currently allows duplicate entries (e.g., ['logo','logo','name']); update the
z.array(z.enum(PANEL_SECTIONS)).max(5).optional() definition to enforce
uniqueness by adding a refinement that checks the array has no duplicates (e.g.,
compare new Set(value).size to value.length) and provide a clear error message;
keep the existing .max(5) and .optional() and apply the refinement on the same
symbol sectionsOrder so callers get validation failures for duplicate sections.

57-60: 💤 Low value

$ResourceLink lacks URL format validation on href.

href accepts any non-empty string up to 2000 chars. Malformed URLs or non-URL strings could slip through. Consider adding .url() if only valid URLs are acceptable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/schemas/src/setup/setup.ts` around lines 57 - 60, The $ResourceLink
schema's href currently allows any non-empty string; update the href validator
to enforce URL format by replacing z.string().min(1).max(2000) with
z.string().url().max(2000) (or z.string().url().min(1).max(2000) if you want to
keep the explicit min), so the $ResourceLink object only accepts well-formed
URLs; locate the $ResourceLink definition to apply this change.
apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx (2)

247-262: 💤 Low value

Consider using index-only key for resource links.

Line 255 generates keys as ${link.href}-${index}, but if two links share the same href, React will warn about duplicate keys. Since the array is admin-controlled and the order is stable, using index alone is sufficient.

🔑 Proposed fix
-            key={`${link.href}-${index}`}
+            key={index}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx` around lines
247 - 262, In LoginBrandingPanel update the map over branding!.resourceLinks! to
use the array index as the React key instead of `${link.href}-${index}` to avoid
duplicate-key warnings when two links share the same href; locate the JSX block
inside the resourceLinks.map in the LoginBrandingPanel component and replace the
key prop with just the index (e.g., key={index}), as the array is
admin-controlled and order is stable.

83-84: 💤 Low value

Consider consistent empty-string handling.

Lines 82, 83, and 84 handle empty strings differently: instanceName uses || to treat empty strings as missing (falling back to the default), while instanceTagline and instanceDetails use ?? which preserves empty strings. Since all three call .trim(), an empty or whitespace-only string becomes '', which is falsy and won't render in the JSX conditions (lines 202, 217). However, the inconsistency may confuse future maintainers.

🔄 Proposed consistency fix
-  const instanceTagline = branding?.instanceTagline?.[lang]?.trim() ?? null;
-  const instanceDetails = branding?.instanceDetails?.[lang]?.trim() ?? null;
+  // eslint-disable-next-line `@typescript-eslint/prefer-nullish-coalescing`
+  const instanceTagline = branding?.instanceTagline?.[lang]?.trim() || null;
+  // eslint-disable-next-line `@typescript-eslint/prefer-nullish-coalescing`
+  const instanceDetails = branding?.instanceDetails?.[lang]?.trim() || null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx` around lines 83
- 84, The three variables instanceName, instanceTagline, and instanceDetails are
handled inconsistently: instanceName uses || to treat empty/whitespace-only
strings as missing while instanceTagline and instanceDetails use ?? which
preserves empty strings; make them consistent by applying the same empty-string
fallback logic to instanceTagline and instanceDetails (e.g., after .trim()
coerce '' to null or fallback value the same way instanceName does) so JSX
rendering conditions behave uniformly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/routes/_app/admin/branding.tsx`:
- Around line 213-215: The code blindly casts saved?.rightPanelTheme to
RightPanelOption which can produce an unselectable state when the API returns a
value not present in RIGHT_PANEL_OPTIONS; update the assignment for
rightPanelOption to validate the saved value against the allowed options
(RIGHT_PANEL_OPTIONS) and only accept it if it is included, otherwise fall back
to 'none' (e.g., check RIGHT_PANEL_OPTIONS.includes(saved?.rightPanelTheme) and
use that value as RightPanelOption only when true, else use 'none'); reference
the rightPanelOption variable, the RightPanelOption type, RIGHT_PANEL_OPTIONS
constant, and the saved.rightPanelTheme source when making this change.
- Around line 180-233: The form is only seeded once from
setupStateQuery.data.branding which causes staleness after refetch; extract the
initialization/migration logic used when creating the useState (the mapping from
saved -> FormState, including legacyUrlInSrc) into a helper (e.g.,
buildFormFromSaved) and add a useEffect that watches
setupStateQuery.data.branding and calls setForm(buildFormFromSaved(saved)) and
also updates savedSnapshotRef.current =
JSON.stringify(buildFormFromSaved(saved)) so the editor rehydrates on async
loads/refetches; ensure the helper is referenced in the initial useState
initializer to avoid duplication and avoid overwriting user edits by only
applying the effect when the incoming saved exists and differs from current
savedSnapshotRef.current if desired.

---

Nitpick comments:
In `@apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx`:
- Around line 247-262: In LoginBrandingPanel update the map over
branding!.resourceLinks! to use the array index as the React key instead of
`${link.href}-${index}` to avoid duplicate-key warnings when two links share the
same href; locate the JSX block inside the resourceLinks.map in the
LoginBrandingPanel component and replace the key prop with just the index (e.g.,
key={index}), as the array is admin-controlled and order is stable.
- Around line 83-84: The three variables instanceName, instanceTagline, and
instanceDetails are handled inconsistently: instanceName uses || to treat
empty/whitespace-only strings as missing while instanceTagline and
instanceDetails use ?? which preserves empty strings; make them consistent by
applying the same empty-string fallback logic to instanceTagline and
instanceDetails (e.g., after .trim() coerce '' to null or fallback value the
same way instanceName does) so JSX rendering conditions behave uniformly.

In `@packages/schemas/src/setup/setup.ts`:
- Line 126: The sectionsOrder schema currently allows duplicate entries (e.g.,
['logo','logo','name']); update the
z.array(z.enum(PANEL_SECTIONS)).max(5).optional() definition to enforce
uniqueness by adding a refinement that checks the array has no duplicates (e.g.,
compare new Set(value).size to value.length) and provide a clear error message;
keep the existing .max(5) and .optional() and apply the refinement on the same
symbol sectionsOrder so callers get validation failures for duplicate sections.
- Around line 57-60: The $ResourceLink schema's href currently allows any
non-empty string; update the href validator to enforce URL format by replacing
z.string().min(1).max(2000) with z.string().url().max(2000) (or
z.string().url().min(1).max(2000) if you want to keep the explicit min), so the
$ResourceLink object only accepts well-formed URLs; locate the $ResourceLink
definition to apply this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ccc5518a-eefc-469a-ac38-438afa78b7d8

📥 Commits

Reviewing files that changed from the base of the PR and between fc7904a and 6912ebe.

📒 Files selected for processing (17)
  • apps/api/prisma/schema.prisma
  • apps/api/src/setup/dto/update-setup-state.dto.ts
  • apps/api/src/setup/setup.service.ts
  • apps/web/src/components/Footer/Footer.tsx
  • apps/web/src/components/Layout/Layout.tsx
  • apps/web/src/components/LoginBranding/LoginBrandingPanel.stories.tsx
  • apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx
  • apps/web/src/components/LoginBranding/index.ts
  • apps/web/src/hooks/useCurrentYear.ts
  • apps/web/src/hooks/useNavItems.ts
  • apps/web/src/hooks/useUpdateSetupStateMutation.ts
  • apps/web/src/route-tree.ts
  • apps/web/src/routes/_app/admin/branding.tsx
  • apps/web/src/routes/_app/admin/settings.tsx
  • apps/web/src/routes/auth/login.tsx
  • apps/web/src/utils/branding.ts
  • packages/schemas/src/setup/setup.ts

Comment thread apps/web/src/routes/_app/admin/branding.tsx Outdated
Comment thread apps/web/src/routes/_app/admin/branding.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new, persisted “login branding” configuration that administrators can edit in-app and that is rendered both on the real login page and in a live preview/editor.

Changes:

  • Adds a new admin route (/admin/branding) to configure login-page branding (content sections, logo, colors/themes, typography, ordering).
  • Updates the login page to render a new <LoginBrandingPanel> on large screens and supports an optional themed right panel.
  • Persists branding via a new BrandingConfig schema (Zod) and a Prisma composite type, plus adds a useCurrentYear hook to keep the footer year up-to-date.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
packages/schemas/src/setup/setup.ts Defines BrandingConfig + supporting enums/types and updates setup-state update schema.
apps/web/src/utils/branding.ts Adds theme color presets and helpers to build gradients from branding config.
apps/web/src/routes/auth/login.tsx Renders the branding panel beside the login form and supports right-panel gradients.
apps/web/src/routes/_app/admin/settings.tsx Removes legacy branding field from settings form (branding moved to new route).
apps/web/src/routes/_app/admin/branding.tsx New admin “Customize Login Page” editor with live preview and persistence.
apps/web/src/route-tree.ts Registers the new /admin/branding route in TanStack Router route tree.
apps/web/src/hooks/useUpdateSetupStateMutation.ts Adds optional custom success notification for setup-state updates.
apps/web/src/hooks/useNavItems.ts Adds admin navigation item linking to “Customize Login Page”.
apps/web/src/hooks/useCurrentYear.ts New hook that keeps the displayed year current across long-running sessions.
apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx New reusable branding panel component used by both login page and admin preview.
apps/web/src/components/LoginBranding/LoginBrandingPanel.stories.tsx Storybook stories for the branding panel component.
apps/web/src/components/LoginBranding/index.ts Barrel export for the branding panel component.
apps/web/src/components/Layout/Layout.tsx Adjusts layout width/overflow handling to avoid horizontal scroll/grey gutter.
apps/web/src/components/Footer/Footer.tsx Uses useCurrentYear instead of a module-scope constant year.
apps/api/src/setup/setup.service.ts Validates branding payload on read and updates composite branding with Prisma set.
apps/api/src/setup/dto/update-setup-state.dto.ts Updates DTO to allow optional branding in PATCH payload.
apps/api/prisma/schema.prisma Adds BrandingConfig composite type and stores it on SetupState.branding.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/schemas/src/setup/setup.ts Outdated
Comment on lines +81 to +87
/** Custom logo height in pixels, used when logoSize is 'custom' */
customLogoHeight: z.number().int().positive().max(5000).nullish(),
/** The uploaded logo image as a data URI (SVG, PNG, JPEG, …); used when logoSource is 'upload' */
customLogoSrc: z.string().max(3_000_000).nullish(),
/** An external logo image URL; used when logoSource is 'url' */
customLogoUrl: z.string().max(2000).nullish(),
/** Custom logo width in pixels, used when logoSize is 'custom' */
Comment thread apps/web/src/routes/_app/admin/branding.tsx Outdated
Comment thread apps/web/src/routes/_app/admin/branding.tsx Outdated
Comment thread apps/web/src/routes/_app/admin/branding.tsx
Comment thread apps/web/src/routes/_app/admin/branding.tsx
Comment thread apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx Outdated
Comment thread apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx Outdated
Comment thread apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx Outdated
@gdevenyi

gdevenyi commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Screenshots!

@thomasbeaudry

Copy link
Copy Markdown
Collaborator Author
Screenshot from 2026-06-03 14-35-17 Screenshot from 2026-06-03 14-35-01 Screenshot from 2026-06-03 14-34-37

@thomasbeaudry

Copy link
Copy Markdown
Collaborator Author

Just some samples, but you have to play with it to get a feel for it

thomasbeaudry and others added 8 commits June 3, 2026 16:38
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Restrict resource-link hrefs to http(s) in $ResourceLink (the server-side
  gate) so a crafted javascript:/data: value can't be persisted and rendered
  into <a href> on the login page.
- Make the unsaved-changes dirty-check cheap: serialize form state via a
  shared snapshotForm() that collapses the (multi-MB) uploaded-logo data URI
  to its length, memoized so it runs once per change instead of twice per
  render. Used by both the guard and the submit-time snapshot to stay in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/schemas/src/setup/setup.ts`:
- Around line 61-66: The current RESOURCE_LINK_URL_PATTERN allows dots in the
path so hosts like "localhost" can pass; update the pattern used by
RESOURCE_LINK_URL_PATTERN (and thus the $ResourceLink.href validator) to require
a dot inside the host portion rather than anywhere in the URL by changing the
regex to enforce no slashes/whitespace in the host and at least one dot in that
host (e.g. require "[^\/\s]+\.[^\/\s]+" for the host) while optionally allowing
a path after it; update RESOURCE_LINK_URL_PATTERN accordingly and run/adjust
relevant tests that validate $ResourceLink.href.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: efcf50a3-ed97-450f-b5ca-65325ae6c2c4

📥 Commits

Reviewing files that changed from the base of the PR and between 6912ebe and 9e58916.

📒 Files selected for processing (3)
  • apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx
  • apps/web/src/routes/_app/admin/branding.tsx
  • packages/schemas/src/setup/setup.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/components/LoginBranding/LoginBrandingPanel.tsx
  • apps/web/src/routes/_app/admin/branding.tsx

Comment thread packages/schemas/src/setup/setup.ts Outdated
thomasbeaudry and others added 11 commits June 4, 2026 14:51
…style after merging main

The rule tightening from the recent eslint-config bump on main surfaced
violations in PR-introduced code once main was merged in:

- packages/schemas/src/setup/setup.ts: hoist the private $HexColor,
  RESOURCE_LINK_URL_PATTERN, and $FontSize const declarations above
  the first export so all exports come last.
- apps/web/src/utils/branding.ts: switch LOGIN_THEME_COLORS from
  Record<K, V> to the mapped-type form to satisfy
  @typescript-eslint/consistent-indexed-object-style.
- apps/web/src/components/LoginBranding/LoginBrandingPanel.stories.tsx:
  move the Storybook default export to the bottom of the file.
- LoginBrandingPanel.tsx, admin/branding.tsx: drop now-unnecessary
  PanelSection[] type assertions (eslint --fix).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… timeout

DELETE requests were incorrectly including Content-Type: application/json
with no body, causing production servers to reject them with 400. The
request timeout was also too short (2s) for force-delete operations that
cascade through instrument records and sessions.
- Add form rehydration via useEffect to prevent stale state after save
- Add sectionsOrder uniqueness refinement in schema
- Add .url() validation on customLogoUrl
- Tighten resource-link URL regex to require dot in host portion
- Replace window.confirm with custom Yes/No dialog (No as default)
- Restructure setup.ts exports to satisfy import/exports-last lint rule
- Fix exports-last in LoginBrandingPanel stories
- Fix Record<> to index signature in branding utils

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 4 comments.

Comment thread packages/schemas/src/setup/setup.ts Outdated

export type DevelopmentReleaseInfo = z.infer<typeof $DevelopmentReleaseInfo>;
export const $DevelopmentReleaseInfo = z.object({
const $ReleaseVersion = z.string().regex(/[0-9]+.[0-9]+.[0-9]+/);
Comment on lines +82 to +87
await this.setupStateModel.update({
data: {
...rest,
// Composite types must be replaced wholesale via `set`
...(branding !== undefined ? { branding: { set: branding ?? null } } : {})
},
Comment on lines +23 to +28
onSuccess() {
addNotification({ type: 'success' });
addNotification({
message: successNotification?.message,
title: successNotification?.title,
type: 'success'
});
* it only changes on an explicit upload/remove, and this keeps the dirty check
* cheap on every keystroke instead of re-stringifying the whole image.
*/
const snapshotForm = (form: FormState): string => JSON.stringify({ ...form, customLogoSrc: form.customLogoSrc.length });
- Make dirty-check snapshot collision-resistant (length + head/tail fingerprint)
- Guard success notification to prevent blank toasts when not provided
- Normalize branding defaults before Prisma set to handle incomplete payloads
- Anchor and escape $ReleaseVersion regex to reject malformed versions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants